#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
视频处理器模块，用于检测和删除视频中的静音片段
"""

import os
import subprocess
import tempfile
import json
import re
from typing import List, Tuple, Dict, Optional


class VideoProcessor:
    """
    视频处理器类，使用FFmpeg检测和删除静音片段
    """
    
    def __init__(self):
        # 检查FFmpeg是否可用
        self._check_ffmpeg()
    
    def _check_ffmpeg(self) -> None:
        """检查FFmpeg是否已安装并可访问"""
        try:
            subprocess.run(
                ["ffmpeg", "-version"], 
                stdout=subprocess.PIPE, 
                stderr=subprocess.PIPE,
                check=True
            )
        except (subprocess.SubprocessError, FileNotFoundError):
            raise RuntimeError("未找到FFmpeg。请确保FFmpeg已安装并添加到系统PATH中。")
    
    def detect_silence(self, input_file: str, silence_threshold: float, min_silence_duration: float) -> List[Dict[str, float]]:
        """
        检测视频中的静音片段
        
        Args:
            input_file: 输入视频文件路径
            silence_threshold: 静音检测阈值（dB）
            min_silence_duration: 最小静音持续时间（秒）
            
        Returns:
            List[Dict[str, float]]: 静音片段列表，每个片段包含开始和结束时间
        """
        # 创建临时文件以存储检测结果
        with tempfile.NamedTemporaryFile(suffix='.txt', delete=False) as temp_file:
            temp_filename = temp_file.name
        
        try:
            # 使用FFmpeg的silencedetect过滤器检测静音
            cmd = [
                "ffmpeg",
                "-i", input_file,
                "-af", f"silencedetect=noise={silence_threshold}dB:d={min_silence_duration}",
                "-f", "null",
                "-"
            ]
            
            # 修改：不使用text=True，使用二进制模式并手动处理解码
            result = subprocess.run(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                check=True
            )
            
            # 从FFmpeg输出中解析静音段，增加错误处理并使用UTF-8解码
            stderr_output = ""
            try:
                stderr_output = result.stderr.decode('utf-8', errors='replace')
            except UnicodeDecodeError:
                # 如果UTF-8失败，尝试使用系统默认编码
                try:
                    stderr_output = result.stderr.decode(errors='replace')
                except Exception as e:
                    print(f"解码FFmpeg输出时出错: {e}")
                    return []
            
            silence_segments = self._parse_silence_segments(stderr_output)
            
            return silence_segments
            
        except subprocess.SubprocessError as e:
            print(f"检测静音时出错: {e}")
            return []
        finally:
            # 清理临时文件
            if os.path.exists(temp_filename):
                try:
                    os.unlink(temp_filename)
                except Exception as e:
                    print(f"删除临时文件时出错: {e}")
    
    def _parse_silence_segments(self, ffmpeg_output: str) -> List[Dict[str, float]]:
        """
        从FFmpeg输出中解析静音段信息
        
        Args:
            ffmpeg_output: FFmpeg的stderr输出
            
        Returns:
            List[Dict[str, float]]: 静音片段列表
        """
        # 修改：增加输入参数验证
        if not ffmpeg_output or not isinstance(ffmpeg_output, str):
            print("无效的FFmpeg输出")
            return []
            
        silence_starts = re.findall(r'silence_start: ([\d\.]+)', ffmpeg_output)
        silence_ends = re.findall(r'silence_end: ([\d\.]+)', ffmpeg_output)
        
        # 如果没有检测到静音段或者起止数量不匹配，则返回空列表
        if not silence_starts or len(silence_starts) != len(silence_ends):
            return []
        
        silence_segments = [
            {"start": float(start), "end": float(end)}
            for start, end in zip(silence_starts, silence_ends)
        ]
        
        return silence_segments
    
    def _get_video_duration(self, input_file: str) -> float:
        """
        获取视频总时长
        
        Args:
            input_file: 输入视频文件路径
            
        Returns:
            float: 视频时长（秒）
        """
        cmd = [
            "ffprobe",
            "-v", "error",
            "-show_entries", "format=duration",
            "-of", "json",
            input_file
        ]
        
        try:
            # 修改：使用二进制模式并手动解码
            result = subprocess.run(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                check=True
            )
            
            stdout_data = result.stdout.decode('utf-8', errors='replace')
            
            if not stdout_data:
                print("无法获取视频时长")
                return 0.0
                
            data = json.loads(stdout_data)
            if 'format' not in data or 'duration' not in data['format']:
                print("视频时长信息不完整")
                return 0.0
                
            duration = float(data['format']['duration'])
            
            return duration
        except json.JSONDecodeError as e:
            print(f"解析JSON时出错: {e}")
            return 0.0
        except Exception as e:
            print(f"获取视频时长时出错: {e}")
            return 0.0

    def _is_audio_only(self, input_file: str) -> bool:
        """
        检查文件是否仅包含音频流（无视频流）
        
        Args:
            input_file: 输入文件路径
            
        Returns:
            bool: 如果文件只包含音频流则返回True
        """
        cmd = [
            "ffprobe",
            "-v", "error",
            "-select_streams", "v:0",  # 选择第一个视频流
            "-show_entries", "stream=codec_type",
            "-of", "json",
            input_file
        ]
        
        try:
            result = subprocess.run(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                check=True
            )
            
            stdout_data = result.stdout.decode('utf-8', errors='replace')
            data = json.loads(stdout_data)
            
            # 如果没有视频流，则返回True（仅音频）
            return 'streams' not in data or len(data['streams']) == 0
            
        except Exception:
            # 出错时假定有视频流
            return False
    
    def _generate_filter_complex(self, silence_segments: List[Dict[str, float]], duration: float, is_audio_only: bool = False) -> str:
        """
        生成FFmpeg的复杂过滤器，用于剪切出非静音片段
        
        Args:
            silence_segments: 静音片段列表
            duration: 视频总时长
            is_audio_only: 是否仅处理音频文件
            
        Returns:
            str: FFmpeg的复杂过滤器字符串
        """
        if not silence_segments or duration <= 0:
            return ""
        
        # 提取非静音片段
        non_silence_segments = []
        current_pos = 0.0
        
        for segment in silence_segments:
            # 如果静音起点大于当前位置，则中间部分是非静音
            if segment["start"] > current_pos:
                non_silence_segments.append({
                    "start": current_pos,
                    "end": segment["start"]
                })
            
            # 更新当前位置到静音结束点
            current_pos = segment["end"]
        
        # 添加最后一个静音段结束到视频结束的非静音段
        if current_pos < duration:
            non_silence_segments.append({
                "start": current_pos,
                "end": duration
            })
        
        # 如果没有非静音段，返回空字符串
        if not non_silence_segments:
            return ""
        
        # 生成过滤器复杂表达式
        filter_parts = []
        concat_parts = []
        
        # 根据是否是纯音频文件生成不同的过滤器
        if is_audio_only:
            # 纯音频文件只需要音频过滤器
            for i, segment in enumerate(non_silence_segments):
                filter_parts.append(f"[0:a]atrim=start={segment['start']}:end={segment['end']},asetpts=PTS-STARTPTS[a{i}]")
                concat_parts.append(f"[a{i}]")
            
            concat_str = f"{''.join(concat_parts)}concat=n={len(non_silence_segments)}:v=0:a=1[outa]"
        else:
            # 视频文件需要同时处理视频和音频流
            for i, segment in enumerate(non_silence_segments):
                filter_parts.append(f"[0:v]trim=start={segment['start']}:end={segment['end']},setpts=PTS-STARTPTS[v{i}]")
                filter_parts.append(f"[0:a]atrim=start={segment['start']}:end={segment['end']},asetpts=PTS-STARTPTS[a{i}]")
                concat_parts.append(f"[v{i}][a{i}]")
            
            concat_str = f"{''.join(concat_parts)}concat=n={len(non_silence_segments)}:v=1:a=1[outv][outa]"
        
        return f"{';'.join(filter_parts)};{concat_str}"
    
    def process_video(self, input_file: str, output_file: str, silence_threshold: float, min_silence_duration: float) -> bool:
        """
        处理视频或音频，移除静音片段
        
        Args:
            input_file: 输入文件路径
            output_file: 输出文件路径
            silence_threshold: 静音检测阈值（dB）
            min_silence_duration: 最小静音持续时间（秒）
            
        Returns:
            bool: 处理是否成功
        """
        try:
            # 检测是否是纯音频文件
            is_audio_only = self._is_audio_only(input_file)
            print(f"文件类型: {'纯音频' if is_audio_only else '视频'}")
            
            # 检测静音段
            silence_segments = self.detect_silence(input_file, silence_threshold, min_silence_duration)
            
            # 如果没有检测到静音段，直接复制原文件
            if not silence_segments:
                print("未检测到静音段，将直接复制原文件")
                cmd = [
                    "ffmpeg",
                    "-i", input_file,
                    "-c", "copy",
                    "-y",
                    output_file
                ]
                
                subprocess.run(cmd, check=True)
                return True
            
            # 获取文件时长
            duration = self._get_video_duration(input_file)
            if duration <= 0:
                print("无法获取有效的文件时长，将直接复制原文件")
                cmd = [
                    "ffmpeg",
                    "-i", input_file,
                    "-c", "copy",
                    "-y",
                    output_file
                ]
                subprocess.run(cmd, check=True)
                return True
            
            # 生成过滤器复杂表达式，传入是否为纯音频文件参数
            filter_complex = self._generate_filter_complex(silence_segments, duration, is_audio_only)
            
            # 如果没有有效的过滤器，直接复制原文件
            if not filter_complex:
                print("无法生成有效的过滤器表达式，将直接复制原文件")
                cmd = [
                    "ffmpeg",
                    "-i", input_file,
                    "-c", "copy",
                    "-y",
                    output_file
                ]
            else:
                print(f"检测到 {len(silence_segments)} 个静音段，开始处理...")
                # 使用生成的复杂过滤器剪切文件
                if is_audio_only:
                    # 纯音频文件只处理音频流
                    cmd = [
                        "ffmpeg",
                        "-i", input_file,
                        "-filter_complex", filter_complex,
                        "-map", "[outa]",
                        "-y",
                        output_file
                    ]
                else:
                    # 视频文件处理视频和音频流
                    cmd = [
                        "ffmpeg",
                        "-i", input_file,
                        "-filter_complex", filter_complex,
                        "-map", "[outv]",
                        "-map", "[outa]",
                        "-y",
                        output_file
                    ]
            
            # 执行FFmpeg命令
            subprocess.run(cmd, check=True)
            return True
            
        except subprocess.SubprocessError as e:
            print(f"处理文件时出错: {e}")
            return False
        except Exception as e:
            print(f"发生意外错误: {e}")
            return False 